package jdbc;

import org.junit.Test;

import java.sql.*;

/**
 * PrepareStatement
 */
public class PreparedStatementDemo {
    /**
     * 演示sql注入
     *
     * @throws Exception
     */
    @Test
    public void test1() throws Exception {
        Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/test_database?useSSL=false&serverTimezone=UTC&allowPublicKeyRetrieval=true",
                "root", "1234");

        Statement stat = conn.createStatement();

        String name = "ewefrefg]";
        String password = "' or '1' = '1"; // password 写入一段特殊的sql代码,导致登录成功
        String sql1 = "select * from tb_user_test where name = '" + name + "' and password = '" + password + "'";
        // 如果受影响的行数大于0则执行成功,否则执行失败
        ResultSet r = stat.executeQuery(sql1); // 执行完DML语句,受影响的行数
        if (r.next()){
            System.out.println("登陆成功");
        } else {
            System.out.println("登陆失败");
        }

        conn.close();
        stat.close();
    }

    /**
     * 使用PreparedStatement解决sql注入问题
     * @throws Exception
     */
    @Test
    public void test2() throws Exception {
        Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/test_database?useSSL=false&serverTimezone=UTC&allowPublicKeyRetrieval=true",
                "root", "1234");


        String name = "fffdfdsfad";
        String password = "' or '1' = '1";
        // 对于sql语句中的参数值使用?占位符来代替
        String sql1 = "select * from tb_user_test where name = ? and password = ?";

        // 使用Connection对象来获取PreparedStatement对象,并传入对于的sql语句
        PreparedStatement ps = conn.prepareStatement(sql1);

        /*
            设置参数值: 对象调用setXXX(参数1,参数2)给?赋值
                XXX:数据类型 如setInt(参数1,参数2)
                参数:
                    参数一:?的位置的编号,从1开始
                    参数二:?的值
            执行sql:
                executeUpdate()/executeQuery();不需要再传递sql语句
         */
        ps.setString(1, name);
        ps.setString(2, password); // 提交到数据库时会进行转义
        ResultSet resultSet = ps.executeQuery();

        if (resultSet.next()){
            System.out.println("登陆成功");
        } else {
            System.out.println("登陆失败");
        }

        conn.close();
        ps.close();
        resultSet.close();
    }

    /**
     *  PreparedStatement的好处:
     *      1.预编译SQL,性能更高:
     *          如果对于一条SQL语句需要反复使用的话,则使用预编译可以有效的提高性能
     *          预编译:
     *              1.PreparedStatement预编译功能开启:useServerPreStmts=true(默认关闭,加载连接数据库的url后)
     *              2.配置MySQL执行日志(查看预编译效果,重启mysql服务后生效):
     *                  log-output=FILE
     *                  general-log=1
     *                  general_log_file="D:\mysql.log"
     *                  slow-query-log=1
     *                  slow_query_log_file="D:\mysql_slow.log"
     *                  long_query_time=2
     *          原理:
     *              1.在获取PreparedStatement对象时,将sql语句发送给mysql服务器进行检查,编译(这些步骤很耗时)
     *              2.执行时就不用再进行这些步骤了,速度更快
     *              3.如果sql模板一样,则只需要进行一次检查、编译
     *      2.防止SQL注入:将敏感字符进行转义
     * @throws Exception
     */
    @Test
    public void test3() throws Exception {
        String url = "jdbc:mysql:///test_database?useSSL=false&useServerPreStmts=true&allowPublicKeyRetrieval=true";
        String username = "root";
        String pwd = "1234";
        Connection conn = DriverManager.getConnection(url, username, pwd);

        String name = "fffdfdsfad";
        String password = "' or '1' = '1";
        // 对于sql语句中的参数值使用?占位符来代替
        String sql1 = "select * from tb_user_test where name = ? and password = ?";

        // 使用Connection对象来获取PreparedStatement对象,并传入对于的sql语句
        PreparedStatement ps = conn.prepareStatement(sql1);

        /*
            设置参数值: 对象调用setXXX(参数1,参数2)给?赋值
                XXX:数据类型 如setInt(参数1,参数2)
                参数:
                    参数一:?的位置的编号,从1开始
                    参数二:?的值
            执行sql:
                executeUpdate()/executeQuery();不需要再传递sql语句
         */
        ps.setString(1, name);
        ps.setString(2, password); // 提交到数据库时会进行转义
        ResultSet resultSet = ps.executeQuery();

        if (resultSet.next()){
            System.out.println("登陆成功");
        } else {
            System.out.println("登陆失败");
        }

        conn.close();
        ps.close();
        resultSet.close();
    }
}
